from datetime import datetime, date, timedelta
from typing import Dict, Any, Optional, Tuple, List
from sqlalchemy.ext.asyncio import AsyncSession
from decimal import Decimal

from module_speech.dao.speech_client_dao import ClientUserDao
from module_speech.dao.speech_usage_dao import ClientUsageDao
from module_speech.entity.do.speech_client_user_do import SpeechClientUserDO
from module_speech.entity.do.speech_client_usage_do import SpeechClientUsageDO
from utils.log_util import logger
from exceptions.exception import ServiceException


class QuotaService:
    """
    配额管理服务层
    """
    
    # 配额相关常量
    QUOTA_WARNING_THRESHOLD = 0.8  # 配额警告阈值（80%）
    QUOTA_CRITICAL_THRESHOLD = 0.95  # 配额临界阈值（95%）
    
    @classmethod
    async def check_quota_available(cls, db: AsyncSession, client_user_id: int,
                                   required_minutes: float = 0.0) -> Dict[str, Any]:
        """
        检查配额是否可用
        
        :param db: 数据库会话
        :param client_user_id: 客户端用户ID
        :param required_minutes: 需要的分钟数
        :return: 配额检查结果
        """
        try:
            # 获取用户信息
            client_user = await ClientUserDao.get_client_user_by_id(db, client_user_id)
            if not client_user:
                raise ServiceException(message="客户端用户不存在")
            
            # 检查用户状态
            if client_user.status != 1:
                return {
                    'available': False,
                    'reason': 'user_disabled',
                    'message': '用户账户已禁用'
                }
            
            # 检查配额是否过期需要重置
            await cls._check_and_reset_quota_if_needed(db, client_user)
            
            # 计算剩余配额
            remaining_quota = client_user.quota_monthly - client_user.quota_used_monthly
            
            # 检查是否有足够配额
            if remaining_quota < required_minutes:
                return {
                    'available': False,
                    'reason': 'insufficient_quota',
                    'message': '配额不足',
                    'quota_monthly': client_user.quota_monthly,
                    'quota_used': client_user.quota_used_monthly,
                    'quota_remaining': remaining_quota,
                    'quota_needed': required_minutes
                }
            
            # 计算配额使用率
            usage_rate = client_user.quota_used_monthly / client_user.quota_monthly if client_user.quota_monthly > 0 else 0
            
            # 确定配额状态
            quota_status = 'normal'
            if usage_rate >= cls.QUOTA_CRITICAL_THRESHOLD:
                quota_status = 'critical'
            elif usage_rate >= cls.QUOTA_WARNING_THRESHOLD:
                quota_status = 'warning'
            
            return {
                'available': True,
                'quota_status': quota_status,
                'quota_monthly': client_user.quota_monthly,
                'quota_used': client_user.quota_used_monthly,
                'quota_remaining': remaining_quota,
                'usage_rate': round(usage_rate * 100, 2),
                'reset_date': client_user.quota_reset_date.isoformat() if client_user.quota_reset_date else None
            }
            
        except Exception as e:
            logger.error(f"检查配额可用性失败: {str(e)}")
            raise ServiceException(message=f"检查配额可用性失败: {str(e)}")
    
    @classmethod
    async def consume_quota(cls, db: AsyncSession, client_user_id: int,
                           consumed_minutes: float, request_info: Dict[str, Any] = None) -> bool:
        """
        消费配额
        
        :param db: 数据库会话
        :param client_user_id: 客户端用户ID
        :param consumed_minutes: 消费的分钟数
        :param request_info: 请求信息（用于记录使用统计）
        :return: 是否消费成功
        """
        try:
            # 获取用户信息
            client_user = await ClientUserDao.get_client_user_by_id(db, client_user_id)
            if not client_user:
                raise ServiceException(message="客户端用户不存在")
            
            # 检查配额是否充足
            quota_check = await cls.check_quota_available(db, client_user_id, consumed_minutes)
            if not quota_check['available']:
                logger.warning(f"配额不足，无法消费: 用户ID {client_user_id}, 需要 {consumed_minutes} 分钟")
                return False
            
            # 更新配额使用量
            client_user.quota_used_monthly += consumed_minutes
            await ClientUserDao.update_client_user_dao(db, client_user)
            
            # 记录使用统计
            if request_info:
                await cls._record_usage_statistics(db, client_user_id, consumed_minutes, request_info)
            
            await db.commit()
            
            logger.info(f"配额消费成功: 用户ID {client_user_id}, 消费 {consumed_minutes} 分钟")
            
            return True
            
        except Exception as e:
            await db.rollback()
            logger.error(f"配额消费失败: {str(e)}")
            raise ServiceException(message=f"配额消费失败: {str(e)}")
    
    @classmethod
    async def get_quota_usage_details(cls, db: AsyncSession, client_user_id: int) -> Dict[str, Any]:
        """
        获取配额使用详情
        
        :param db: 数据库会话
        :param client_user_id: 客户端用户ID
        :return: 配额使用详情
        """
        try:
            # 获取用户信息
            client_user = await ClientUserDao.get_client_user_by_id(db, client_user_id)
            if not client_user:
                raise ServiceException(message="客户端用户不存在")
            
            # 获取当月使用统计
            current_date = date.today()
            monthly_usage = await ClientUsageDao.get_monthly_usage(
                db, client_user_id, current_date.year, current_date.month
            )
            
            # 获取今日使用统计
            today_usage = await ClientUsageDao.get_daily_usage(db, client_user_id, current_date)
            
            # 计算配额信息
            usage_rate = client_user.quota_used_monthly / client_user.quota_monthly if client_user.quota_monthly > 0 else 0
            remaining_quota = max(0, client_user.quota_monthly - client_user.quota_used_monthly)
            
            # 预测用尽日期
            predicted_exhaustion_date = cls._predict_quota_exhaustion(
                client_user.quota_used_monthly, 
                client_user.quota_monthly,
                client_user.quota_reset_date
            )
            
            return {
                'user_info': {
                    'client_user_id': client_user_id,
                    'plan_type': client_user.plan_type,
                    'status': client_user.status
                },
                'quota_info': {
                    'monthly_quota': client_user.quota_monthly,
                    'used_quota': client_user.quota_used_monthly,
                    'remaining_quota': remaining_quota,
                    'usage_rate': round(usage_rate * 100, 2),
                    'reset_date': client_user.quota_reset_date.isoformat() if client_user.quota_reset_date else None,
                    'predicted_exhaustion_date': predicted_exhaustion_date
                },
                'usage_stats': {
                    'monthly_requests': monthly_usage.total_requests if monthly_usage else 0,
                    'monthly_minutes': monthly_usage.total_audio_minutes if monthly_usage else 0.0,
                    'today_requests': today_usage.request_count if today_usage else 0,
                    'today_minutes': today_usage.audio_duration_minutes if today_usage else 0.0
                },
                'rate_limiting': {
                    'requests_per_minute': client_user.rate_limit_per_minute,
                    'current_hour_requests': await cls._get_current_hour_requests(db, client_user_id)
                }
            }
            
        except Exception as e:
            logger.error(f"获取配额使用详情失败: {str(e)}")
            raise ServiceException(message=f"获取配额使用详情失败: {str(e)}")
    
    @classmethod
    async def reset_monthly_quota(cls, db: AsyncSession, client_user_id: int) -> bool:
        """
        重置月度配额
        
        :param db: 数据库会话
        :param client_user_id: 客户端用户ID
        :return: 是否重置成功
        """
        try:
            # 获取用户信息
            client_user = await ClientUserDao.get_client_user_by_id(db, client_user_id)
            if not client_user:
                raise ServiceException(message="客户端用户不存在")
            
            # 重置配额使用量
            old_used_quota = client_user.quota_used_monthly
            client_user.quota_used_monthly = 0
            client_user.quota_reset_date = date.today() + timedelta(days=30)  # 设置下次重置日期
            
            # 保存更新
            await ClientUserDao.update_client_user_dao(db, client_user)
            await db.commit()
            
            logger.info(f"配额重置成功: 用户ID {client_user_id}, 原使用量 {old_used_quota} 分钟")
            
            return True
            
        except Exception as e:
            await db.rollback()
            logger.error(f"配额重置失败: {str(e)}")
            raise ServiceException(message=f"配额重置失败: {str(e)}")
    
    @classmethod
    async def batch_reset_expired_quotas(cls, db: AsyncSession) -> Dict[str, int]:
        """
        批量重置过期的配额
        
        :param db: 数据库会话
        :return: 重置统计信息
        """
        try:
            # 获取需要重置配额的用户
            today = date.today()
            users_to_reset = await ClientUserDao.get_users_with_expired_quota(db, today)
            
            reset_stats = {
                'total': len(users_to_reset),
                'success': 0,
                'failed': 0
            }
            
            for user in users_to_reset:
                try:
                    success = await cls.reset_monthly_quota(db, user.client_user_id)
                    if success:
                        reset_stats['success'] += 1
                    else:
                        reset_stats['failed'] += 1
                except Exception as e:
                    logger.error(f"重置用户 {user.client_user_id} 配额失败: {str(e)}")
                    reset_stats['failed'] += 1
            
            logger.info(f"批量配额重置完成: {reset_stats}")
            return reset_stats
            
        except Exception as e:
            logger.error(f"批量重置过期配额失败: {str(e)}")
            raise ServiceException(message=f"批量重置过期配额失败: {str(e)}")
    
    @classmethod
    async def adjust_quota(cls, db: AsyncSession, client_user_id: int, 
                          new_monthly_quota: int, reason: str = None) -> bool:
        """
        调整用户配额
        
        :param db: 数据库会话
        :param client_user_id: 客户端用户ID
        :param new_monthly_quota: 新的月度配额
        :param reason: 调整原因
        :return: 是否调整成功
        """
        try:
            # 获取用户信息
            client_user = await ClientUserDao.get_client_user_by_id(db, client_user_id)
            if not client_user:
                raise ServiceException(message="客户端用户不存在")
            
            old_quota = client_user.quota_monthly
            client_user.quota_monthly = new_monthly_quota
            
            # 如果新配额小于已使用量，需要处理
            if client_user.quota_used_monthly > new_monthly_quota:
                logger.warning(f"新配额 {new_monthly_quota} 小于已使用量 {client_user.quota_used_monthly}")
                # 可以选择重置使用量或者保持当前状态
            
            # 保存更新
            await ClientUserDao.update_client_user_dao(db, client_user)
            await db.commit()
            
            logger.info(f"配额调整成功: 用户ID {client_user_id}, {old_quota} -> {new_monthly_quota}, 原因: {reason}")
            
            return True
            
        except Exception as e:
            await db.rollback()
            logger.error(f"配额调整失败: {str(e)}")
            raise ServiceException(message=f"配额调整失败: {str(e)}")
    
    @classmethod
    async def get_quota_alerts(cls, db: AsyncSession) -> List[Dict[str, Any]]:
        """
        获取配额告警信息
        
        :param db: 数据库会话
        :return: 告警列表
        """
        try:
            alerts = []
            
            # 获取配额使用率高的用户
            high_usage_users = await ClientUserDao.get_users_with_high_quota_usage(
                db, cls.QUOTA_WARNING_THRESHOLD
            )
            
            for user in high_usage_users:
                usage_rate = user.quota_used_monthly / user.quota_monthly if user.quota_monthly > 0 else 0
                
                alert_level = 'critical' if usage_rate >= cls.QUOTA_CRITICAL_THRESHOLD else 'warning'
                
                alerts.append({
                    'client_user_id': user.client_user_id,
                    'username': user.username,
                    'alert_level': alert_level,
                    'usage_rate': round(usage_rate * 100, 2),
                    'quota_monthly': user.quota_monthly,
                    'quota_used': user.quota_used_monthly,
                    'quota_remaining': user.quota_monthly - user.quota_used_monthly,
                    'plan_type': user.plan_type,
                    'reset_date': user.quota_reset_date.isoformat() if user.quota_reset_date else None
                })
            
            return alerts
            
        except Exception as e:
            logger.error(f"获取配额告警信息失败: {str(e)}")
            raise ServiceException(message=f"获取配额告警信息失败: {str(e)}")
    
    @classmethod
    async def _check_and_reset_quota_if_needed(cls, db: AsyncSession, client_user: SpeechClientUserDO):
        """
        检查并在需要时重置配额
        """
        if client_user.quota_reset_date and client_user.quota_reset_date <= date.today():
            await cls.reset_monthly_quota(db, client_user.client_user_id)
    
    @classmethod
    async def _record_usage_statistics(cls, db: AsyncSession, client_user_id: int,
                                     consumed_minutes: float, request_info: Dict[str, Any]):
        """
        记录使用统计
        """
        try:
            # 获取或创建今日使用记录
            today = date.today()
            usage_record = await ClientUsageDao.get_daily_usage(db, client_user_id, today)
            
            if usage_record:
                # 更新现有记录
                usage_record.request_count += 1
                usage_record.audio_duration_minutes += consumed_minutes
                if request_info.get('success', True):
                    usage_record.success_count += 1
                else:
                    usage_record.failed_count += 1
                
                # 更新其他统计信息
                if 'response_time' in request_info:
                    usage_record.api_response_time_avg = (
                        usage_record.api_response_time_avg + request_info['response_time']
                    ) / 2
                
                if 'data_transfer_mb' in request_info:
                    usage_record.data_transfer_mb += request_info['data_transfer_mb']
                
                await ClientUsageDao.update_usage_record_dao(db, usage_record)
            else:
                # 创建新记录
                new_usage = SpeechClientUsageDO(
                    client_user_id=client_user_id,
                    usage_date=today,
                    request_count=1,
                    success_count=1 if request_info.get('success', True) else 0,
                    failed_count=0 if request_info.get('success', True) else 1,
                    audio_duration_minutes=consumed_minutes,
                    model_used=request_info.get('model_name', ''),
                    feature_used=request_info.get('feature', 'upload'),
                    api_response_time_avg=request_info.get('response_time', 0.0),
                    data_transfer_mb=request_info.get('data_transfer_mb', 0.0),
                    create_time=datetime.now(),
                    update_time=datetime.now()
                )
                await ClientUsageDao.add_usage_record_dao(db, new_usage)
                
        except Exception as e:
            logger.error(f"记录使用统计失败: {str(e)}")
            # 不抛出异常，避免影响主流程
    
    @classmethod
    def _predict_quota_exhaustion(cls, used_quota: float, total_quota: float,
                                 reset_date: date) -> Optional[str]:
        """
        预测配额用尽日期
        """
        try:
            if used_quota <= 0 or total_quota <= 0 or used_quota >= total_quota:
                return None
            
            remaining_quota = total_quota - used_quota
            days_since_reset = 30 - (reset_date - date.today()).days if reset_date else 30
            
            if days_since_reset <= 0:
                return None
            
            daily_usage_rate = used_quota / days_since_reset
            if daily_usage_rate <= 0:
                return None
            
            days_to_exhaustion = remaining_quota / daily_usage_rate
            exhaustion_date = date.today() + timedelta(days=int(days_to_exhaustion))
            
            return exhaustion_date.isoformat()
            
        except Exception:
            return None
    
    @classmethod
    async def _get_current_hour_requests(cls, db: AsyncSession, client_user_id: int) -> int:
        """
        获取当前小时的请求数
        """
        try:
            current_hour = datetime.now().replace(minute=0, second=0, microsecond=0)
            return await ClientUsageDao.get_hourly_requests(db, client_user_id, current_hour)
        except Exception:
            return 0 